{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 日志刷屏使我痛苦，我开发了VLog😋"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "训练日志刷屏使我痛苦，我开发了VLog，可以在任意训练代码中轻松使用~\n",
    "\n",
    "例如，通过回调嵌入到lightgbm/catboost/transformers/ultralytics，乃至keras库的训练代码流程中~\n",
    "\n",
    "\n",
    "**before**:\n",
    "\n",
    "![](./data/before.png)\n",
    "\n",
    "\n",
    "\n",
    "**after**：\n",
    "\n",
    "![](./data/torchkeras_plot.gif)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**为什么不用tensorboard或者wandb?**\n",
    "\n",
    "tensorboard需要开端口权限，服务器开发环境有时候没有端口权限~\n",
    "\n",
    "wandb需要联网，有时候网速很差或者没有网，影响体验~\n",
    "\n",
    "综合对比考虑如下表\n",
    "\n",
    "|日志方案    | 学习成本|  指标直观性 |是否需要端口权限|是否需要联网 |推荐星级|\n",
    "|:---------|:------:|:-------------:|:---------:|:---------:|:-----|\n",
    "|print      | 无需学习 |不直观,易刷屏|不需要    |不需要     |⭐️|\n",
    "|tensorboard| 较难学习 |比较直观,跨页面      |需要       |不需要     |⭐️⭐️|\n",
    "|wandb      | 较好学习 |比较直观,跨页面      |不需要     |需要       |⭐️⭐️⭐️⭐️|\n",
    "|VLog😋     | 极易学习 |非常直观,同页面      |不需要     |不需要      |⭐️⭐️⭐️⭐️⭐️|\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#!pip install -U lightgbm \n",
    "#!pip install ultralytics\n",
    "#!pip install git+https://github.com/lyhue1991/torchkeras "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 一，VLog基本原理"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "VLog类主要有以下5个方法。\n",
    "\n",
    "```python\n",
    "\n",
    "from torchkeras import VLog\n",
    "\n",
    "#1, 初始化方法\n",
    "vlog = VLog(epochs=20, monitor_metric='val_loss', monitor_mode='min') \n",
    "\n",
    "#2, 显示开始空图表\n",
    "vlog.log_start()\n",
    "\n",
    "#3, 更新step级别日志\n",
    "vlog.log_step({'train_loss':0.003,'val_loss':0.002}) \n",
    "\n",
    "#4, 更新epoch级别日志\n",
    "vlog.log_epoch({'train_acc':0.9,'val_acc':0.87,'train_loss':0.002,'val_loss':0.03})\n",
    "\n",
    "#5, 输出最终稳定状态图表\n",
    "vlog.log_end()\n",
    "```\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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Uab/i82bN6dy5crMnz/f7Ofj999/JywszPRaPTw8aN++PV999RVnzpwxe47C6rUoiKCgIOrUqcPChQvNEuB58+ahaRp9+/Y1ax8fH8+JEydo06ZNUYcqQFapiMKXPUO9QYMGqkePHuqzzz5TTz31lALUk08+adZ22rRpClBt2rRR06dPV/PmzVNvvvmmqlmzpvr444+VUkqtWbNGValSRY0cOVJ9/vnnas6cOapFixbKzs5Obd++3XSt7t27K2dnZzVjxgy1bNkytWPHjjvG+t133ylAubi4qB49etxyfsKECQpQgwcPVgsXLlSvvvqqqlChgqpevbq6//77Te0Ka5XKX3/9pWxtbVWdOnXUxx9/rD744APl4eGhypcvr06ePKmUUio2Nta0ImfmzJlq4cKFql+/fgpQM2bMKNB7djdSU1NVUFCQ0jRNDR48WM2fP1+NHj1aOTg4KG9v71uKPU2aNElNmjRJ9e/f37RyIftYTtkrZnKaNm2aGjhwoJozZ476/PPP1YMPPqgANXny5FxjO3DggALU2LFj84y/INf87LPPFKD69u2rvvjiC/XMM88o4JaVVXmpX7++GjBggNmxlJQU5ebmptasWWM6NnXqVGVjY6MAZWtrq2bPnm1aNfPggw+a/u2zXbhwQVWrVk05OTmpESNGqAULFqhp06apxx9/XJUvX97ULnuVSoMGDVTDhg3VJ598osaOHascHBxUYGCgunr1qqnt119/rQDVqlUrNWvWLDVu3Djl5OSk/P39VWxsrKndvn37VLly5VTFihXVuHHj1MKFC9Xbb7+tGjVqZGqTV+Gv7OfIq4hXtri4ONPPyEMPPaQA9cYbb6hJkyapuXPnmrX95ZdflKZpqmPHjmrhwoXqtddeUzY2Nur555+/5borV65UgAoPD7/t8wvLkIRDFLrsXzaHDx9Wffv2VS4uLqp8+fJq+PDhZkvusq1atUq1a9dOOTs7K2dnZ1WnTh01bNgwdfToUaWUUidPnlRDhgxRNWrUUA4ODqpChQqqQ4cO6u+//za7zpEjR1T79u2Vo6Njrks5c5OQkGBq/913391yPiUlRb3xxhvK29tbOTo6qrZt26rt27er+++/3yIJh1JK/f3336pt27bK0dFRubq6qh49eqjDhw+bzqempqoxY8aoRo0aKRcXF+Xs7KwaNWqkPv/8c1Ob/L5n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",
      "text/plain": [
       "<Figure size 600x400 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "\n",
       "<style>\n",
       "    /* background: */\n",
       "    progress::-webkit-progress-bar {background-color: #CDCDCD; width: 100%;}\n",
       "    progress {background-color: #CDCDCD;}\n",
       "\n",
       "    /* value: */\n",
       "    progress::-webkit-progress-value {background-color: #00BFFF  !important;}\n",
       "    progress::-moz-progress-bar {background-color: #00BFFF  !important;}\n",
       "    progress {color: #00BFFF ;}\n",
       "\n",
       "    /* optional */\n",
       "    .progress-bar-interrupted, .progress-bar-interrupted::-webkit-progress-bar {\n",
       "        background: #000000;\n",
       "    }\n",
       "</style>\n"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "\n",
       "    <div>\n",
       "      <progress value='10' class='' max='10' style='width:300px; height:20px; vertical-align: middle;'></progress>\n",
       "      100% [10/10] [00:29]\n",
       "      <br>\n",
       "      \n",
       "    </div>\n",
       "    "
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>val_loss</th>\n",
       "      <th>train_loss</th>\n",
       "      <th>epoch</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>99.175805</td>\n",
       "      <td>100.803715</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>97.334170</td>\n",
       "      <td>98.115262</td>\n",
       "      <td>2</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>95.346233</td>\n",
       "      <td>94.875238</td>\n",
       "      <td>3</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>94.949246</td>\n",
       "      <td>91.809134</td>\n",
       "      <td>4</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>92.467559</td>\n",
       "      <td>89.092992</td>\n",
       "      <td>5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>89.060268</td>\n",
       "      <td>87.629074</td>\n",
       "      <td>6</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>88.117432</td>\n",
       "      <td>85.265212</td>\n",
       "      <td>7</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>85.445769</td>\n",
       "      <td>83.205913</td>\n",
       "      <td>8</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>83.124590</td>\n",
       "      <td>80.787413</td>\n",
       "      <td>9</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>81.979003</td>\n",
       "      <td>77.028675</td>\n",
       "      <td>10</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "    val_loss  train_loss  epoch\n",
       "0  99.175805  100.803715      1\n",
       "1  97.334170   98.115262      2\n",
       "2  95.346233   94.875238      3\n",
       "3  94.949246   91.809134      4\n",
       "4  92.467559   89.092992      5\n",
       "5  89.060268   87.629074      6\n",
       "6  88.117432   85.265212      7\n",
       "7  85.445769   83.205913      8\n",
       "8  83.124590   80.787413      9\n",
       "9  81.979003   77.028675     10"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import time\n",
    "import math,random\n",
    "from torchkeras import VLog\n",
    "\n",
    "epochs = 10\n",
    "batchs = 30\n",
    "\n",
    "#0, 指定监控北极星指标，以及指标优化方向\n",
    "vlog = VLog(epochs, monitor_metric='val_loss', monitor_mode='min') \n",
    "\n",
    "#1, log_start 初始化动态图表\n",
    "vlog.log_start() \n",
    "\n",
    "for epoch in range(epochs):\n",
    "    \n",
    "    #train\n",
    "    for step in range(batchs):\n",
    "        \n",
    "        #2, log_step 更新step级别日志信息，打日志，并用小进度条显示进度\n",
    "        vlog.log_step({'train_loss':100-2.5*epoch+math.sin(2*step/batchs)}) \n",
    "        time.sleep(0.05)\n",
    "        \n",
    "    #eval    \n",
    "    for step in range(20):\n",
    "        \n",
    "        #3, log_step 更新step级别日志信息，指定training=False说明在验证模式，只打日志不更新小进度条\n",
    "        vlog.log_step({'val_loss':100-2*epoch+math.sin(2*step/batchs)},training=False)\n",
    "        time.sleep(0.05)\n",
    "        \n",
    "    #4, log_epoch 更新epoch级别日志信息，每个epoch刷新一次动态图表和大进度条进度\n",
    "    vlog.log_epoch({'val_loss':100 - 2*epoch+2*random.random()-1,\n",
    "                    'train_loss':100-2.5*epoch+2*random.random()-1})  \n",
    "\n",
    "# 5, log_end 调整坐标轴范围，输出最终指标可视化图表\n",
    "vlog.log_end()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 二，在LightGBM中使用VLog "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "设计一个简单的回调，就可以搞定~"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from torchkeras import VLog\n",
    "class VLogCallback:\n",
    "    def __init__(self, num_boost_round, \n",
    "                 monitor_metric='val_loss',\n",
    "                 monitor_mode='min'):\n",
    "        self.order = 20\n",
    "        self.num_boost_round = num_boost_round\n",
    "        self.vlog = VLog(epochs = num_boost_round, monitor_metric = monitor_metric, \n",
    "                         monitor_mode = monitor_mode)\n",
    "\n",
    "    def __call__(self, env) -> None:\n",
    "        metrics = {}\n",
    "        for item in env.evaluation_result_list:\n",
    "            if len(item) == 4:\n",
    "                data_name, eval_name, result = item[:3]\n",
    "                metrics[data_name+'_'+eval_name] = result\n",
    "            else:\n",
    "                data_name, eval_name = item[1].split()\n",
    "                res_mean = item[2]\n",
    "                res_stdv = item[4]\n",
    "                metrics[data_name+'_'+eval_name] = res_mean\n",
    "        self.vlog.log_epoch(metrics)\n",
    "        "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "b1076dfc-b9ad-4769-8c92-a6c4dae69d19",
    "_uuid": "8f2839f25d086af736a60e9eeb907d3b93b6e0e5"
   },
   "outputs": [],
   "source": [
    "import datetime\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import lightgbm as lgb\n",
    "from sklearn import datasets\n",
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "def printlog(info):\n",
    "    nowtime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')\n",
    "    print(\"\\n\"+\"==========\"*8 + \"%s\"%nowtime)\n",
    "    print(info+'...\\n\\n')\n",
    "\n",
    "#================================================================================\n",
    "# 一，读取数据\n",
    "#================================================================================\n",
    "printlog(\"step1: reading data...\")\n",
    "\n",
    "# 读取dftrain,dftest\n",
    "breast = datasets.load_breast_cancer()\n",
    "df = pd.DataFrame(breast.data,columns = [x.replace(' ','_') for x in breast.feature_names])\n",
    "df['label'] = breast.target\n",
    "df['mean_radius'] = df['mean_radius'].apply(lambda x:int(x))\n",
    "df['mean_texture'] = df['mean_texture'].apply(lambda x:int(x))\n",
    "dftrain,dftest = train_test_split(df)\n",
    "\n",
    "categorical_features = ['mean_radius','mean_texture']\n",
    "lgb_train = lgb.Dataset(dftrain.drop(['label'],axis = 1),label=dftrain['label'],\n",
    "                        categorical_feature = categorical_features)\n",
    "\n",
    "lgb_valid = lgb.Dataset(dftest.drop(['label'],axis = 1),label=dftest['label'],\n",
    "                        categorical_feature = categorical_features,\n",
    "                        reference=lgb_train)\n",
    "\n",
    "#================================================================================\n",
    "# 二，设置参数\n",
    "#================================================================================\n",
    "printlog(\"step2: setting parameters...\")\n",
    "                               \n",
    "boost_round = 50                   \n",
    "early_stop_rounds = 10\n",
    "\n",
    "params = {\n",
    "    'boosting_type': 'gbdt',\n",
    "    'objective':'binary',\n",
    "    'metric': ['auc'], #'l2'\n",
    "    'num_leaves': 15,   \n",
    "    'learning_rate': 0.05,\n",
    "    'feature_fraction': 0.9,\n",
    "    'bagging_fraction': 0.8,\n",
    "    'bagging_freq': 5,\n",
    "    'verbose': 0,\n",
    "    'early_stopping_round':5\n",
    "}\n",
    "\n",
    "#================================================================================\n",
    "# 三，训练模型\n",
    "#================================================================================\n",
    "printlog(\"step3: training model...\")\n",
    "\n",
    "result = {}\n",
    "\n",
    "vlog_cb = VLogCallback(boost_round, monitor_metric = 'val_auc', monitor_mode = 'max')\n",
    "vlog_cb.vlog.log_start()\n",
    "\n",
    "gbm = lgb.train(params,\n",
    "                lgb_train,\n",
    "                num_boost_round= boost_round,\n",
    "                valid_sets=(lgb_valid, lgb_train),\n",
    "                valid_names=('val','train'),\n",
    "                callbacks = [lgb.record_evaluation(result),\n",
    "                             vlog_cb]\n",
    "               )\n",
    "\n",
    "vlog_cb.vlog.log_end()\n",
    "\n",
    "#================================================================================\n",
    "# 四，评估模型\n",
    "#================================================================================\n",
    "printlog(\"step4: evaluating model ...\")\n",
    "\n",
    "y_pred_train = gbm.predict(dftrain.drop('label',axis = 1), num_iteration=gbm.best_iteration)\n",
    "y_pred_test = gbm.predict(dftest.drop('label',axis = 1), num_iteration=gbm.best_iteration)\n",
    "\n",
    "print('train accuracy: {:.5} '.format(accuracy_score(dftrain['label'],y_pred_train>0.5)))\n",
    "print('valid accuracy: {:.5} \\n'.format(accuracy_score(dftest['label'],y_pred_test>0.5)))\n",
    "\n",
    "\n",
    "#================================================================================\n",
    "# 五，保存模型\n",
    "#================================================================================\n",
    "printlog(\"step5: saving model ...\")\n",
    "\n",
    "\n",
    "model_dir = \"gbm.model\"\n",
    "print(\"model_dir: %s\"%model_dir)\n",
    "gbm.save_model(\"gbm.model\")\n",
    "printlog(\"task end...\")\n",
    "\n",
    "###\n",
    "##\n",
    "#"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 三， 在ultralytics中使用VLog"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "写个适配的回调~\n",
    "\n",
    "ultralytics可以做 分类，检测，分割 等等。\n",
    "\n",
    "这个回调函数是通用的，此处以分类问题为例，改个monitor_metric即可~\n",
    "\n",
    "\n",
    "cats_vs_dogs数据集可以在公众号算法美食屋后台回复:**torchkeras** 获取~ \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from torchkeras import VLog\n",
    "class VLogCallback:\n",
    "    def __init__(self,epochs,monitor_metric,monitor_mode):\n",
    "        self.vlog = VLog(epochs,monitor_metric,monitor_mode)\n",
    "        \n",
    "    def on_train_batch_end(self,trainer):\n",
    "        self.vlog.log_step(trainer.label_loss_items(trainer.tloss, prefix='train'))\n",
    "\n",
    "    def on_fit_epoch_end(self,trainer):\n",
    "        metrics = {k.split('/')[-1]:v for k,v in trainer.metrics.items() if 'loss' not in k}\n",
    "        self.vlog.log_epoch(metrics)\n",
    "\n",
    "    def on_train_epoch_end(self,trainer):\n",
    "        pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ultralytics import YOLO \n",
    "epochs = 10\n",
    "\n",
    "vlog_cb = VLogCallback(epochs = epochs,\n",
    "                       monitor_metric='accuracy_top1',\n",
    "                       monitor_mode='max')\n",
    "callbacks = {\n",
    "    \"on_train_batch_end\": vlog_cb.on_train_batch_end,\n",
    "    \"on_fit_epoch_end\": vlog_cb.on_fit_epoch_end\n",
    "}\n",
    "\n",
    "model = YOLO(model = 'yolov8n-cls.pt')\n",
    "for event,func in callbacks.items():\n",
    "    model.add_callback(event,func)\n",
    "    \n",
    "vlog_cb.vlog.log_start()\n",
    "results = model.train(data='cats_vs_dogs', \n",
    "                      epochs=epochs, workers=4)     # train the model\n",
    "vlog_cb.vlog.log_end()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 四， 在transformers中使用VLog "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "waimai评论数据集可以在公众号算法美食屋后台回复:**torchkeras** 获取~ "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#回调给你写好了~\n",
    "from torchkeras.tools.transformers import VLogCallback "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np \n",
    "import pandas as pd \n",
    "import torch \n",
    "import datasets \n",
    "from transformers import AutoTokenizer,DataCollatorWithPadding\n",
    "from transformers import AutoModelForSequenceClassification \n",
    "from transformers import TrainingArguments,Trainer \n",
    "from transformers import EarlyStoppingCallback\n",
    "\n",
    "from tqdm import tqdm \n",
    "from transformers import AdamW, get_scheduler\n",
    "\n",
    "\n",
    "#一，准备数据\n",
    "\n",
    "df = pd.read_csv(\"waimai_10k.csv\")\n",
    "ds = datasets.Dataset.from_pandas(df)\n",
    "ds = ds.shuffle(42) \n",
    "ds = ds.rename_columns({\"review\":\"text\",\"label\":\"labels\"})\n",
    "\n",
    "tokenizer = AutoTokenizer.from_pretrained('bert-base-chinese') \n",
    "\n",
    "ds_encoded = ds.map(lambda example:tokenizer(example[\"text\"]),\n",
    "                    remove_columns = [\"text\"],\n",
    "                    batched=True)\n",
    "\n",
    "#train,val,test split\n",
    "ds_train_val,ds_test = ds_encoded.train_test_split(test_size=0.2).values()\n",
    "ds_train,ds_val = ds_train_val.train_test_split(test_size=0.2).values() \n",
    "\n",
    "data_collator = DataCollatorWithPadding(tokenizer=tokenizer)\n",
    "\n",
    "dl_train = torch.utils.data.DataLoader(ds_train, batch_size=16, collate_fn = data_collator)\n",
    "dl_val = torch.utils.data.DataLoader(ds_val, batch_size=16,  collate_fn = data_collator)\n",
    "dl_test = torch.utils.data.DataLoader(ds_test, batch_size=16,  collate_fn = data_collator)\n",
    "\n",
    "for batch in dl_train:\n",
    "    break\n",
    "print({k: v.shape for k, v in batch.items()})\n",
    "\n",
    "\n",
    "\n",
    "#二，定义模型\n",
    "model = AutoModelForSequenceClassification.from_pretrained(\n",
    "    'bert-base-chinese',num_labels=2)\n",
    "\n",
    "#三，训练模型\n",
    "def compute_metrics(eval_preds):\n",
    "    logits, labels = eval_preds\n",
    "    preds = np.argmax(logits, axis=-1)\n",
    "    accuracy = np.sum(preds==labels)/len(labels)\n",
    "    precision = np.sum((preds==1)&(labels==1))/np.sum(preds==1)\n",
    "    recall = np.sum((preds==1)&(labels==1))/np.sum(labels==1)\n",
    "    f1  = 2*recall*precision/(recall+precision)\n",
    "    return {\"accuracy\":accuracy,\"precision\":precision,\"recall\":recall,'f1':f1}\n",
    "\n",
    "training_args = TrainingArguments(\n",
    "    output_dir = \"bert_waimai\",\n",
    "    num_train_epochs = 3,\n",
    "    logging_steps = 20,\n",
    "    gradient_accumulation_steps = 10,\n",
    "    evaluation_strategy=\"steps\", #epoch\n",
    "    \n",
    "    metric_for_best_model='eval_f1',\n",
    "    greater_is_better=True,\n",
    "    \n",
    "    report_to='none',\n",
    "    load_best_model_at_end=True\n",
    ")\n",
    "\n",
    "callbacks = [EarlyStoppingCallback(early_stopping_patience=10),\n",
    "             VLogCallback()] #监控指标同 metric_for_best_model\n",
    "\n",
    "trainer = Trainer(\n",
    "    model,\n",
    "    training_args,\n",
    "    train_dataset=ds_train,\n",
    "    eval_dataset=ds_val,\n",
    "    compute_metrics=compute_metrics,\n",
    "    callbacks = callbacks,\n",
    "    data_collator=data_collator,\n",
    "    tokenizer=tokenizer,\n",
    ")\n",
    "trainer.train() \n",
    "\n",
    "\n",
    "\n",
    "#四，评估模型\n",
    "trainer.evaluate(ds_val)\n",
    "\n",
    "\n",
    "#五，使用模型\n",
    "from transformers import pipeline\n",
    "model.config.id2label = {0:\"差评\",1:\"好评\"}\n",
    "classifier = pipeline(task=\"text-classification\",tokenizer = tokenizer,model=model.cpu())\n",
    "classifier(\"挺好吃的哦\")\n",
    "\n",
    "#六，保存模型\n",
    "model.save_pretrained(\"waimai_10k_bert\")\n",
    "tokenizer.save_pretrained(\"waimai_10k_bert\")\n",
    "\n",
    "classifier = pipeline(\"text-classification\",model=\"waimai_10k_bert\")\n",
    "classifier([\"味道还不错，下次再来\",\"我去，吃了我吐了三天\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**如果本项目对你有所帮助，想鼓励一下作者，记得给本项目加一颗星星star⭐️，并分享给你的朋友们喔😊!** \n",
    "\n",
    "如果在torchkeras的使用中遇到问题，可以在项目中提交issue。\n",
    "\n",
    "如果想要获得更快的反馈或者与其他torchkeras用户小伙伴进行交流，\n",
    "\n",
    "可以在公众号算法美食屋后台回复关键字：**加群**。\n",
    "\n",
    "![](https://tva1.sinaimg.cn/large/e6c9d24egy1h41m2zugguj20k00b9q46.jpg)\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
